<!DOCTYPE html>
<html>
  <head>
    <title>threejs</title>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=2,maximum-scale=1,user-scalable=yes" />
    <metahttp-equiv ="X-UA-Compatible" content="IE=edge,chrome=1" />

    <style>
      html,
      body {
        padding: 0;
        margin: 0;
        overflow: hidden;
      }

      canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>

  <body>
    <script type="module">
      import * as THREE from "https://cdn.skypack.dev/three@v0.129.0";
      import { GLTFLoader } from "https://cdn.skypack.dev/three@v0.129.0/examples//jsm/loaders/GLTFLoader.js";
      import { OrbitControls } from "https://cdn.skypack.dev/three@v0.129.0/examples/jsm/controls/OrbitControls.js";
      import { BufferGeometryUtils } from "https://cdn.skypack.dev/three@v0.129.0/examples/jsm/utils/BufferGeometryUtils.js";

      // 创建渲染器
      var renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight); // 设置canvas画布大小
      renderer.setPixelRatio(window.devicePixelRatio); // 设置像素比，图元光栅化时采样的像素点更多（可以推断出物理像素），画面更精细
      document.body.appendChild(renderer.domElement); // canvas画布插入dom树

      // 创建场景
      var scene = new THREE.Scene();
      scene.background = new THREE.Color("#347897");

      // 辅助线
      var axisHelper = new THREE.AxisHelper(500);
      scene.add(axisHelper);

      // 添加点光源
      let light1 = new THREE.PointLight("#fff", 0.8);
      light1.position.set(2160, 1160, 123120);
      //scene.add(light1);

      //环境光
      let ambient = new THREE.AmbientLight("#fff", 0.7);
      scene.add(ambient);

      // 创建相机
      var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 10000);
      camera.position.set(0, 830, 1070); // 设置相机位置

      // 创建控制器
      let controls = new OrbitControls(camera, renderer.domElement);
      controls.autoRotate = true;

      // 渲染
      !(function render() {
        controls.update(); // Update controls
        renderer.render(scene, camera);
        requestAnimationFrame(render);
      })();

      // 底座
      const g = new THREE.SphereGeometry(6378137 / 10000, 100, 100); // 球的半径为地球半径缩小10000倍
      const m = new THREE.MeshStandardMaterial({
        map: new THREE.TextureLoader().load("http://182.43.179.137:81/public/images/texture-earth2.png"),
        //blending: THREE.AdditiveBlending,
      });
      scene.add(new THREE.Mesh(g, m));

      /**
       * 经纬度转地固坐标
       */
      function lon2xyz(longitude, latitude, R = 6378137) {
        var lon = (longitude * Math.PI) / 180;
        var lat = (latitude * Math.PI) / 180;

        var x = R * Math.cos(lat) * Math.cos(lon);
        var y = R * Math.cos(lat) * Math.sin(lon);
        var z = R * Math.sin(lat);
        return { x: x, y: y, z: z };
      }

      // 加载地理信息数据
      var loader = new THREE.FileLoader().setResponseType("json");
      loader.load("http://182.43.179.137:81/public/map/世界--国家级行政区域--地理信息数据--.json", function (data) {
        // 遍历
        data.features.forEach((country) => {
          const flyPoints = [];
          if (country.geometry) {
            // 格式一致
            if (country.geometry.type === "Polygon") {
              country.geometry.coordinates = [country.geometry.coordinates];
            }

            // 遍历
            country.geometry.coordinates.forEach((borderShapeArr) => {
              borderShapeArr.forEach((borderShape) => {
                const points = [];
                borderShape.forEach((coord, index) => {
                  let point = lon2xyz(coord[0], coord[1]); //经纬度转地固坐标
                  // 修正姿态
                  let { x, y, z } = new THREE.Vector3(point.x, point.y, point.z).applyAxisAngle(new THREE.Vector3(1, 0, 0), -Math.PI / 2);
                  x = (x / 10000) * 1.0001;
                  y = (y / 10000) * 1.0001;
                  z = (z / 10000) * 1.0001;
                  points.push(new THREE.Vector3(x, y, z)); // 地球半径缩小10000倍,再放大1.001倍
                  flyPoints.push([x, y, z]);
                });

                // 边界
                const material = new THREE.LineBasicMaterial({ color: "#eeee00" });
                const geometry = new THREE.BufferGeometry().setFromPoints(points);
                const line = new THREE.Line(geometry, material);
                scene.add(line);
              });
            });

            // 中国边界飞线
            if (country.properties.name != "China") return;
            // 遍历
            const curvePath = new THREE.CurvePath(); // 曲线
            flyPoints.forEach((point, index) => {
              if (index > flyPoints.length - 2) return;
              const [x, y, z] = point;
              const [x1, y1, z1] = flyPoints[index + 1];
              const v1 = new THREE.Vector3(x, y, z);
              const v2 = new THREE.Vector3(x1, y1, z1);
              curvePath.add(new THREE.LineCurve3(v1, v2));
            });

            // 采样5000个点
            const pointArr = [];
            curvePath.getSpacedPoints(10000).forEach((point) => {
              const { x, y, z } = point;
              pointArr.push([x, y, z]);
            });

            const length = 600; // 长度
            const circle = 13; // 周期
            const color = "#FF00FF"; // 颜色
            const opacity = 1; // 透明度
            const size = 5; // 尺寸

            const fly = new Fly(pointArr, length, circle, color, opacity, size);
            fly.move();
            scene.add(fly.obj);
          }
        });
      });

      // 飞线
      class Fly {
        constructor(points, length, circle = 2, color = "#ff00ff", opacity = 1, size = 10) {
          this.points = points; // 路径
          this.length = length; // 长度（粒子数）
          this.circle = circle; // 周期
          this.color = color; // 颜色
          this.opacity = opacity; // 透明度
          this.size = size; // 大小
          this.progress = 0;
          this.frameId = null;
          this.geometry = null;
          this.material = null;
          this.texture = null;
          this.obj = null;
          this.createFly();
        }

        createFly() {
          // 几何体
          this.geometry = new THREE.BufferGeometry();
          this.updateFly();

          // 纹理和材质
          this.texture = new THREE.TextureLoader().load("http://182.43.179.137:81/public/images/texture-circle-gradient1.png");
          this.material = new THREE.PointsMaterial({
            color: this.color,
            map: this.texture,
            // alphaTest: 0.9,
            transparent: true,
            depthWrite: false,
            opacity: this.opacity,
            //blending: THREE.AdditiveBlending,
            size: this.size,
            sizeAttenuation: true,
          });

          // 修正着色器
          this.material.onBeforeCompile = (shader) => {
            const vertex = `
              attribute float aScale;
              void main() {
            `;
            const vertex1 = `gl_PointSize = size * aScale;`;
            shader.vertexShader = shader.vertexShader.replace("void main() {", vertex);
            shader.vertexShader = shader.vertexShader.replace("gl_PointSize = size;", vertex1);
          };

          // 物体
          this.obj = new THREE.Points(this.geometry, this.material);
        }

        // 更新
        updateFly() {
          // 计算新数据
          const posArr = [];
          const scaleArr = [];
          const posIndex = Math.floor(this.progress * this.points.length);
          const flyPointArr = this.points.filter((point, index) => {
            if (index >= posIndex && index <= posIndex + this.length) return true;
          });
          flyPointArr.forEach((point, index) => {
            posArr.push(...point);
            scaleArr.push((index + 1) / this.length);
          });
          // 更新几何体
          this.geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(posArr), 3));
          this.geometry.setAttribute("aScale", new THREE.BufferAttribute(new Float32Array(scaleArr), 1));
        }

        // 移动
        move() {
          if (this.frameId) return;
          const clock = new THREE.Clock(); // 时钟
          const h = () => {
            this.frameId = requestAnimationFrame(h);
            const dt = clock.getDelta();
            this.progress += dt / this.circle; // 更新进度
            if (this.progress > 1) this.progress = 1;
            this.updateFly();
            if (this.progress == 1) this.progress = 0;
          };
          this.frameId = requestAnimationFrame(h);
        }

        // 停止
        stop() {
          if (this.frameId) {
            cancelAnimationFrame(this.frameId);
            this.frameId = null;
          }
        }
      }
    </script>
  </body>
</html>
